<template>
  <div class="topo">
    <div class="chat" ref="chatRef"></div>
    <div class="tooltip" id="tooltip" style="display: none" ref="tooltipRef"></div>
  </div>
</template>
<script setup>
import * as d3 from 'd3'

const chatRef = ref(null)
const tooltipRef = ref(null)

const props = defineProps({
  data: { type: Object },
})

onMounted(() => {
  drawChart()
})

let svg

function drawChart() {
  if (svg) {
    svg.remove()
    svg = null
  }

  const topologyData = props.data

  // Prepare nodes and links
  const nodes = []
  const links = []

  // 判断是否有
  let isAttackStage = false

  for (const key in topologyData.topologyid) {
    const item = topologyData.topologyid[key]
    if (item.attackstage) {
      isAttackStage = true
      break
    }
  }

  if (isAttackStage) {
    for (const key in topologyData.topologyid) {
      const data = topologyData.topologyid[key]
      const id = key

      if (data.attackstage) {
        nodes.push({
          id: id,
          type: 'attackstage',
          label: data.comm,
          data: data,
        })
      } else {
        nodes.push({
          id: id,
          type: 'other',
          label: data.comm,
          data: data,
        })
      }
    }
  } else {
    for (const key in topologyData.topologyid) {
      const data = topologyData.topologyid[key]
      const id = key

      nodes.push({
        id: id,
        type: {
          进程: 'process',
          文件: 'file',
          网络: 'network',
        }[data.nodetype],
        label: data.comm,
        data: data,
      })
    }
  }

  // Add links
  for (const link of topologyData.related) {
    let type = link.attribute

    const data = topologyData.topologyid[link.end - 1]

    if (link.attackstage) {
      type += '-攻击步骤' + link.attackstage
    }

    links.push({
      source: link.start.toString(),
      target: link.end.toString(),
      type,
    })
  }

  // Create the graph
  // let width = document.body.clientWidth
  // let height = document.querySelector('#graph').clientHeight
  // const svg = d3.select('#graph').append('svg').attr('width', width).attr('height', height)

  const width = chatRef.value.clientWidth
  const height = chatRef.value.clientHeight

  svg = d3.select(chatRef.value).append('svg').attr('width', width).attr('height', height)

  const g = svg.append('g')

  // Create a tooltip div
  // const tooltip = d3.select('#tooltip')
  // const tooltip = d3.select(tooltipRef.value)
  const tooltip = d3
    .select('body')
    .append('div')
    .attr('class', 'tooltip')
    .style('position', 'absolute')
    .style('display', 'none')

  // Create a simulation with forces
  const simulation = d3
    .forceSimulation(nodes)
    .force(
      'link',
      d3
        .forceLink(links)
        .id(d => d.id)
        .distance(150),
    )
    .force('charge', d3.forceManyBody().strength(-500))
    .force('center', d3.forceCenter(width / 2, height / 2))
    .force('x', d3.forceX(width / 2).strength(0.05))
    .force('y', d3.forceY(height / 2).strength(0.05))
    .force(
      'collision',
      d3.forceCollide().radius(d => {
        const max = Math.min(230, Math.max(150, d.label.length * 7))
        // Math.max(150, d.label.length * 7)

        return max / 2 + 10
      }),
    )

  // 添加箭头标记定义
  g.append('defs')
    .append('marker')
    .attr('id', 'arrowhead')
    .attr('viewBox', '0 -5 10 10') // 视图框
    .attr('refX', 10) // 箭头尖端的位置
    .attr('refY', 0)
    .attr('markerWidth', 6)
    .attr('markerHeight', 6)
    .attr('orient', 'auto') // 自动旋转以匹配线条方向
    .append('path')
    .attr('d', 'M0,-5L10,0L0,5') // 箭头的路径
    .attr('fill', '#D6DBDF') // 箭头颜色与线条一致

  // 攻击步骤箭头
  g.append('defs')
    .append('marker')
    .attr('id', 'arrowhead-attack')
    .attr('viewBox', '0 -5 10 10')
    .attr('refX', 10)
    .attr('refY', 0)
    .attr('markerWidth', 6)
    .attr('markerHeight', 6)
    .attr('orient', 'auto')
    .append('path')
    .attr('d', 'M0,-5L10,0L0,5')
    .attr('fill', '#E74C3C') // 红色

  // Draw links
  const link = g
    .append('g')
    .selectAll('line')
    .data(links)
    .enter()
    .append('line')
    // .attr('stroke-width', 1)
    // .attr('stroke', '#D6DBDF')

    .attr('stroke-width', d => {
      if (d.type.indexOf('攻击步骤') != -1) return 2
      return 1
    })

    .attr('stroke', d => {
      if (d.type.indexOf('攻击步骤') != -1) return '#E74C3C' // 红色
      return '#D6DBDF' // 默认颜色
    })

    // .attr('marker-end', 'url(#arrowhead)')
    .attr('marker-end', d => {
      return d.type.indexOf('攻击步骤') != -1 ? 'url(#arrowhead-attack)' : 'url(#arrowhead-default)'
    })

  // Add link labels
  const linkLabels = g
    .append('g')
    .selectAll('text')
    .data(links)
    .enter()
    .append('text')
    .text(d => d.type)
    .attr('font-size', 10)
    .attr('fill', '#555')

  // Create node groups
  const node = g
    .append('g')
    .selectAll('.node')
    .data(nodes)
    .enter()
    .append('g')
    .attr('class', d => `node ${d.type}`)
    .call(d3.drag().on('start', dragstarted).on('drag', dragged).on('end', dragended))

  // Add rectangles for nodes
  node
    .append('rect')
    // .attr('width', d => Math.max(150, d.label.length * 7))
    // .attr('height', 35)

    .attr('width', d => Math.min(230, Math.max(150, d.label.length * 7))) // 限制最大宽度为300
    .attr('height', 35)
    // .attr('height', d => {
    //   const charWidth = 7 // 假设每个字符宽度
    //   const maxWidth = 300 // 最大宽度
    //   const lineHeight = 20 // 每行高度
    //   const baseHeight = 35 // 基础高度

    //   // 计算需要的行数
    //   const lineCount = Math.ceil((d.label.length * charWidth) / maxWidth)

    //   // 返回调整后的高度
    //   return baseHeight + (lineCount - 1) * lineHeight
    // })

    .attr('rx', 5)
    .attr('ry', 5)
    .on('mouseover', showTooltip)
    .on('mouseout', hideTooltip)

  // Add text for nodes
  node
    .append('text')
    .attr('dy', 20)
    .attr('dx', 10)
    .text(d => {
      // 超过32个字符时截断，加省略
      if (d.label.length > 32) {
        return `${d.label.substring(0, 32)}...`
      }

      return d.label
    })
    .attr('font-size', 12)

  // Update positions on simulation tick
  simulation.on('tick', () => {
    // Keep nodes within the bounds of the SVG
    nodes.forEach(d => {
      d.x = Math.max(
        Math.max(150, d.label.length * 7) / 2,
        Math.min(width - Math.max(150, d.label.length * 7) / 2, d.x),
      )
      d.y = Math.max(20, Math.min(height - 20, d.y))
    })

    link
      .attr('x1', d => d.source.x)
      .attr('y1', d => d.source.y)
      .attr('x2', d => d.target.x)
      .attr('y2', d => d.target.y)

    // Calculate label position with offset
    linkLabels
      .attr('x', d => (d.source.x + d.target.x) / 2 + 10)
      .attr('y', d => (d.source.y + d.target.y) / 2 - 10)

    node.attr('transform', d => `translate(${d.x - Math.max(150, d.label.length * 7) / 2},${d.y - 20})`)
  })

  // Drag functions
  function dragstarted(event, d) {
    if (!event.active) simulation.alphaTarget(0.3).restart()
    d.fx = d.x
    d.fy = d.y
  }

  function dragged(event, d) {
    d.fx = event.x
    d.fy = event.y
  }

  function dragended(event, d) {
    if (!event.active) simulation.alphaTarget(0)
    d.fx = null
    d.fy = null
  }

  // Tooltip functions
  function showTooltip(event, d) {
    let tooltipContent = `<strong>${d.label}</strong><br>`
    for (const [key, value] of Object.entries(d.data)) {
      if (value) {
        tooltipContent += `<strong>${key}:</strong> ${value}<br>`
      }
    }

    // tooltip
    //   .html(tooltipContent)
    //   .style('left', event.offsetX + 'px')
    //   .style('top', event.offsetY + 'px')
    //   .style('display', 'block')

    tooltip
      .html(tooltipContent)
      .style('left', event.pageX + 10 + 'px')
      .style('top', event.pageY + 10 + 'px')
      .style('display', 'block')
  }

  function hideTooltip() {
    tooltip.style('display', 'none')
  }
}

watch(
  () => props.data,
  () => {
    if (props.data) drawChart()
  },
  {
    deep: true,
  },
)
</script>
<style lang="scss" scoped>
.topo {
  height: 100%;
  display: flex;
  flex-direction: column;
  .chat {
    height: 100%;
    flex: 1;
    position: relative;
  }
}
</style>

<style lang="scss">
.node rect {
  stroke: #333;
  stroke-width: 1.5px;
  fill: #fff;
}

.node.process {
  rect {
    fill: rgba($color: #8fb6fd, $alpha: 0.1);
    stroke: #8fb6fd;
  }
  text {
    // fill: #8fb6fd;
  }
}

.node.file {
  rect {
    fill: rgba($color: #e6a23c, $alpha: 0.1);
    stroke: #e6a23c;
  }
  text {
    fill: #e6a23c;
  }
}

.node.network {
  rect {
    fill: rgba($color: #9c27b0, $alpha: 0.1);
    stroke: #9c27b0;
  }
  text {
    fill: #9c27b0;
  }
}

.node.attackstage {
  rect {
    fill: rgba($color: #f56c6c, $alpha: 0.1);
    stroke: #f56c6c;
  }
  text {
    fill: #f56c6c;
  }
}

.node.other {
  rect {
    fill: rgba($color: #606266, $alpha: 0.1);
    stroke: #606266;
  }
  text {
    fill: #333;
  }
}

.edgePath path {
  stroke: #333;
  stroke-width: 1.5px;
  fill: none;
}

.node text {
  font-size: 12px;
}

.tooltip {
  position: absolute;
  padding: 8px;
  background: rgba(0, 0, 0, 0.8);
  color: #fff;
  border-radius: 4px;
  pointer-events: none;
  font-size: 12px;
  max-width: 300px;
  z-index: 66666666;
}
</style>
